package cn.jolyoulu.adminweb.aspects;

import cn.jolyoulu.common.core.utils.net.IPUtils;
import cn.jolyoulu.common.web.entity.ResultInfo;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import cn.jolyoulu.cmnservice.entity.po.SysOperateLog;
import cn.jolyoulu.cmnservice.serivce.SysIpRangeService;
import cn.jolyoulu.cmnservice.serivce.SysOperateLogService;
import cn.jolyoulu.cmnservice.utils.HoldUserUtils;

import java.time.LocalDateTime;
import java.util.*;

/**
 * @Author LuZhouJin
 * @Date 2023/3/21
 * 操作日志
 */
@Slf4j
@Aspect
@Component
public class OperateLogAspect {

    private static final String[] jsonResultExclude = {"base64Img", "token"};

    private static final Set<String> urlExclude = new HashSet<>(Arrays.asList("/api/auth/login", "/api/common/verifyCodeImg"));

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private SysOperateLogService sysOperateLogService;

    @Autowired
    private SysIpRangeService sysIpRangeService;

    @Pointcut(value = "execution(* cn.jolyoulu.adminweb.controller..*(..))")
    private void loginPointcut() {
    }

    @Around("loginPointcut()")
    public Object loginPointcutAround(ProceedingJoinPoint pjp) throws Throwable {
        SysOperateLog opLog = new SysOperateLog();
        long start = System.currentTimeMillis();
        try {
            Object res = pjp.proceed();
            succeed(opLog, res);
            return res;
        } catch (Exception e) {
            error(opLog, e);
            throw e;
        } finally {
            long end = System.currentTimeMillis();
            opLog.setCostTime((int) (end - start));
            addLog(opLog, pjp);
        }
    }

    /**
     * 记录正常返回结果
     */
    private void succeed(SysOperateLog opLog, Object res) {
        opLog.setStatus(0);
        if (res instanceof ResultInfo) {
            String jsonString = JSON.toJSONString(((ResultInfo<?>) res).getData());
            for (String s : jsonResultExclude) {
                jsonString = jsonString.replaceAll("\"" + s + "\":.*,", "");
            }
            opLog.setJsonResult(jsonString);
        }
    }

    /**
     * 记录异常结果
     */
    private void error(SysOperateLog opLog, Exception e) {
        opLog.setStatus(1);
        opLog.setErrorMsg(e.getMessage());
    }

    /**
     * 保存记录
     */
    private void addLog(SysOperateLog opLog, ProceedingJoinPoint pjp) {
        try {
            if (Objects.isNull(HoldUserUtils.get().getUserId())) {
                return;
            }
            if (urlExclude.contains(request.getRequestURI())) {
                return;
            }
            //获取方法签名
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            opLog.setUserId(HoldUserUtils.get().getUserId());
            opLog.setReqMethod(request.getMethod());
            //替换掉路径中的参数
            String reqUrl = request.getRequestURI().replaceAll("[0-9a-z]{32}", "{PathVariable}");
            opLog.setReqUrl(reqUrl);
            String args = Arrays.toString(pjp.getArgs());
            if (args.length() > 500) { //内存超出截取掉,并且打印警告日志
                args = args.substring(0, 500);
                log.warn("操作日志记录警告：ages参数长度 {} ,超出数据库字段长度", args.length());
            }
            opLog.setReqParam(args);
            opLog.setOperateMethod(signature.getDeclaringTypeName() + "." + signature.getName());
            String clientIP = "127.0.0.1";
            opLog.setOperateIp(clientIP);
            opLog.setOperateLocation(sysIpRangeService.getLocationByIPNum(IPUtils.ipToNumber(clientIP)));
            opLog.setOperateTime(LocalDateTime.now());
            sysOperateLogService.save(opLog);
        } catch (Exception e) {
            log.error("操作日志记录失败", e);
        }
    }


}
